package com.hxb.example.aop;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ClassUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;

@Slf4j
@Aspect
@Component
public class FiledDecOrEncAop {

    private record Test(String name){}
    
    @Before(value = "@annotation(com.hxb.example.aop.EnabledEncrypt)")
    public void beforeAdvice(JoinPoint joinPoint) throws IllegalAccessException {
        Object[] args = joinPoint.getArgs();
        log.info("=====>args: {}", args);
        for (Object arg : args) {
            delFiled(arg, EncField.class, (val) -> val + "1111");
        }
    }

    @AfterReturning(value = "@annotation(com.hxb.example.aop.EnabledDecrypt)", returning = "resObj")
    public Object afterReturningAdvice(JoinPoint joinPoint, Object resObj) throws IllegalAccessException {
        log.info("=====>retObj: {}", resObj);
        delFiled(resObj, DecField.class, val -> val + "222");
        return resObj;
    }

    private void delFiled(Object obj, Class<? extends Annotation> annotation, Function<Object, Object> function) throws IllegalAccessException {
        if (checkBasicType(obj)) {
            return;
        }
        if (obj instanceof List list) {
            for (Object o : list) {
                delFiled(o, annotation, function);
            }
            return;
        }
        if (obj instanceof Map map) {
            for (Object o : map.values()) {
                delFiled(o, annotation, function);
            }
            return;
        }
        Field[] fields = obj.getClass().getDeclaredFields();
        for (Field field : fields) {
            if (Objects.isNull(field)) {
                continue;
            }
            field.setAccessible(true);
            Object oldVal = field.get(obj);
            if (field.isAnnotationPresent(annotation)) {
                Object newVal = function.apply(oldVal);
                field.set(obj, newVal);
            } else {
                delFiled(oldVal, annotation, function);
            }
            field.setAccessible(false);
        }
    }

    private boolean checkBasicType(Object obj) {
        return Objects.isNull(obj) || ClassUtils.isPrimitiveOrWrapper(obj.getClass())
                || obj.getClass().isAssignableFrom(String.class);
    }
}
